#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <libgen.h>
#include <dirent.h>
#include <pthread.h>
#include <semaphore.h>
#include <assert.h>
#include <ctype.h>

#include <arpa/inet.h>

#include <sys/types.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/vfs.h>
#include <sys/mount.h>

#include <linux/unistd.h>
#include <linux/if.h>
#include <linux/wireless.h>
#include <netinet/in.h>

#include "img_upgrade.h"
#include "getopt.h"
#include "bb_api.h"
#include "chk.h"
#include "crc.h"
#include "prj_rpc.h"

#define     GPT_FLASH_OFFSET        (0x8000)
#define     GPT_FLASH_SIZE          (0x1000)

#define HERE printf("%s %d\n",__FUNCTION__,__LINE__);

static bb_dev_handle_t *bb_handle;
static int signal_exit = 0;

void pair_demo_Usage(char * name)
{
    printf("Usage: %s [options]\n", name);
    printf("Options:\n");
    printf("\t-h       Print help info \n");
    printf("\t-f       path\n");
}

static void signal_hander(uint32_t signal)
{
    if(signal_exit == 0)
    {
        printf("__Application will exit by signal:%d, pid:%d\n", signal, getpid());

        signal_exit = 1;
    }
}

uint64_t MID_UTIL_Get_Time_ms(void)
{
    struct timeval tv;
	uint64_t ts_ms;
    gettimeofday(&tv, NULL);
	ts_ms = (uint64_t)tv.tv_sec*1000 + tv.tv_usec/1000;
    return ts_ms;
}


int MID_UTIL_CreateDetachThread(ThreadEntryPtrType entry, void *para, pthread_t *pid)
{
    pthread_t ThreadId;
    pthread_attr_t attr;

    pthread_attr_init(&attr);
    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if(pthread_create(&ThreadId, &attr, entry, para) == 0) {
        pthread_attr_destroy(&attr);
		if(pid)
			*pid = ThreadId;
		return 0;
    }
    pthread_attr_destroy(&attr);
    return -1;
}


/* send crc32 as 0 for terminatting the file transfer */
int ar8030_upgrade_done(uint32_t crc32)
{
    int ret = 0;
    bb_set_prj_dispatch2_in_t set_prj;
	prj_rpc_hdr_t *hdr;
    prj_cmd_upgrade_psram_done_in_t* upgrade_psram_done_in;

    hdr=(prj_rpc_hdr_t *)set_prj.data;
    hdr->cmdid = PRJ_CMD_SET_PSRAM_DONE;
    upgrade_psram_done_in = (prj_cmd_upgrade_psram_done_in_t*)hdr->data;
    upgrade_psram_done_in->crc32 = htonl(crc32);
    ret = bb_ioctl(bb_handle, BB_SET_PRJ_DISPATCH2, &set_prj, NULL);
    if  (ret != 0) {
        PRINT_ERR("upgrade_PSRAM_DONE failed, ret = %d", ret);
    }

    return ret;
}

static int ar8030_upgrade_img(int offset, int length)
{
    int ret;
    bb_set_prj_dispatch2_in_t set_prj;
	prj_rpc_hdr_t *hdr;
	prj_cmd_set_psram_update_t *update;

    hdr=(prj_rpc_hdr_t *)set_prj.data;
    hdr->cmdid = PRJ_CMD_SET_PSRAM_UPDATE;

    update = (prj_cmd_set_psram_update_t*)hdr->data;
    update->action = htons(PRJ_PSRAM_ACTION_UPGRADE);
    update->offset = htons(offset);
    update->length = htons(length);

    ret = bb_ioctl(bb_handle, BB_SET_PRJ_DISPATCH2, &set_prj, NULL);

    return ret;
}

int ar80030_upgrade_psram_with_daemon(uint8_t* data, uint32_t file_len)
{
    int ret;
    int i;
    int dev_num;
    bb_host_t* phost=NULL;
    bb_dev_t** devs=NULL;
    int offset = 0;
    int max_size = 0;
    int block_num = 0;
    int block_len = 0;
    uint32_t crc32 = 0;

    bb_set_prj_dispatch2_in_t set_prj;
	prj_rpc_hdr_t *hdr;

    prj_cmd_upgrade_psram_start_in_t *upgrade_psram_start_in;
    prj_cmd_upgrade_psram_data_in_t *upgrade_psram_data_in;

	ret = bb_host_connect(&phost,"127.0.0.1",BB_PORT_DEFAULT);
	if(ret)
	{
		PRINT_ERR("bb connect error,ret=%d",ret);
		//bb_deinit();
		return -1;
	}

    dev_num = bb_dev_getlist(phost,&devs);

    bb_handle = bb_dev_open(devs[0]);
    if (!bb_handle) {
		PRINT_ERR("bb_dev_open error");
		//bb_deinit();
		return -1;
    }

    bb_dev_freelist(devs);

    hdr=(prj_rpc_hdr_t *)set_prj.data;
    hdr->cmdid = PRJ_CMD_SET_PSRAM_START;

    upgrade_psram_start_in = (prj_cmd_upgrade_psram_start_in_t*)hdr->data;
    memset(upgrade_psram_start_in, 0, sizeof(prj_cmd_upgrade_psram_start_in_t));
    upgrade_psram_start_in->len = htonl(file_len);

    strncpy(upgrade_psram_start_in->fname, "ar8030.img", sizeof(upgrade_psram_start_in->fname));

    ret = bb_ioctl(bb_handle, BB_SET_PRJ_DISPATCH2, &set_prj, NULL);
    if (ret < 0) {
        PRINT_ERR("bb_ioctl upgrade_PSRAM_START error ret %d");
        goto END;
    }

    crc32 = getcrc32(data, file_len);
    max_size = sizeof(upgrade_psram_data_in->data);
    block_num = ((file_len / max_size) + !!(file_len % max_size));

    // split
    for (i = 0; i < block_num; ++i) {
        // printf("update process %3.2f%%\r", (float)offset * 100 / file_len);
        offset = i * max_size;

        if ((i == (block_num - 1)) && ((file_len % max_size) != 0)) {
            block_len = (file_len % max_size);
        }
        else {
            block_len = max_size;
        }

        hdr=(prj_rpc_hdr_t *)set_prj.data;
        hdr->cmdid = PRJ_CMD_SET_PSRAM_DATA;
        upgrade_psram_data_in = (prj_cmd_upgrade_psram_data_in_t*)hdr->data;
        upgrade_psram_data_in->len = htons(block_len);
        upgrade_psram_data_in->seq = htons(i);
        memcpy(upgrade_psram_data_in->data, &data[offset], block_len);
        ret = bb_ioctl(bb_handle, BB_SET_PRJ_DISPATCH2, &set_prj, NULL);
        if (ret < 0) {
            PRINT_ERR("bb_ioctl upgrade_PSRAM_DATA error ret %d");
            goto END;
        }
    }

    ret = ar8030_upgrade_done(crc32);
    if (ret < 0) {
        PRINT_ERR("ar8030_upgrade_done error ret %d");
        goto END;
    }

    /*upgrade the img*/
    ret = ar8030_upgrade_img(0, file_len);
    if (ret < 0) {
        PRINT_ERR("ar8030_upgrade_img error ret %d");
        goto END;
    }

END:
    bb_dev_close(bb_handle);

    bb_host_disconnect(phost);

    return ret;
}

int main(int argc, char ** argv)
{
    int ret;
    char path[256] = {0};
    FILE *fp = NULL;
    int file_len = 0;
    char *data = NULL;
    struct img8030 img8030_s;
    bool with_daemon = false;
    bool with_usb = false;

    if(argc == 1){
	    pair_demo_Usage(argv[0]);
		return -1;
	}

    while((ret = getopt(argc, argv, "hf:du")) != -1)
    {
        switch(ret)
		{
            case 'h':
                pair_demo_Usage(argv[0]);
                return 0;
            case 'f':
                strncpy(path, optarg, sizeof(path));
                PRINT_INFO("filename %s", path);
                break;
            case 'd':
                with_daemon = true;
                PRINT_INFO("upgrade with daemon");
                break;
            case 'u':
                with_usb = true;
                PRINT_INFO("upgrade with usb");
                break;
            default:
                PRINT_INFO("unknow cmd param!");
    			pair_demo_Usage(argv[0]);
                return -1;
        }
    }

    PRINT_INFO("%s compiled at: %s %s\n", argv[0], __DATE__, __TIME__);


    fp = fopen(path, "rb");
    if (fp == NULL) {
        PRINT_ERR("open %s failed!", path);
        return 0;
    }

    if (0 != fseek(fp, 0, SEEK_END)) {
        fclose(fp);
        return -1;
    }
    file_len = ftell(fp);

    PRINT_INFO("file_len = %d", file_len);

    data = malloc(file_len);
    if(!data) {
        PRINT_ERR();
        goto END;
    }
    memset(data, 0, file_len);

    if(0 != fseek(fp, 0, SEEK_SET)) {
        PRINT_ERR();
        goto END;
    }

    if (fread(data, 1, file_len, fp) != (file_len)) {
        PRINT_ERR();
        goto END;
    }

    dump_upgrade_file(data);

    memset(&img8030_s, 0, sizeof(img8030_s));

    /* Get the img info and check whether it is valid */
    ret = do_verify_image(data, &img8030_s);
    if (ret < 0) {
        PRINT_ERR();
        goto END;
    }

    if (with_daemon) {
        ret = ar80030_upgrade_psram_with_daemon(data, file_len);
        if (ret) {
            printf("ar80030_upgrade_psram_with_daemon failed, ret %d\n", ret);
        }
        else {
            printf("ar80030_upgrade_psram_with_daemon success!\n");
        }
    }

END:
    fclose(fp);

    if (data) {
        free(data);
    }

    exit(0);
}
